/**
 * 
 */
package il.ac.bgu.cs.heuristics.graph;

import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Predicate;
import il.ac.bgu.cs.heuristics.graph.types.ContradictingStatesException;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.Collections;
import java.util.Set;

import org.apache.log4j.Logger;

/**
 * an immutable state implementation
 * 
 * <p>
 * A state consists of a set of {@link Substitution}, and a reference to a
 * global state which holds more substitutions
 * </p>
 * 
 * @author Ronen Pinko
 * 
 */
public class State implements IState { // NOPMD builder pattern

	/** The substitutions that consists the state */
	protected Set<ISubstitution>	substitutions;

	private final static Logger		LOG	= Logger.getLogger(State.class);

	State(Builder builder) {
		this.substitutions = builder.substitutions;
	}

	@Override
	public Set<ISubstitution> getSubstitutions() {
		return Collections.unmodifiableSet(substitutions);
	}

	public static final class Builder {

		/** The substitutions that consists the state */
		private final Set<ISubstitution>	substitutions;

		/**
		 * deep copies the state and loads it in the builder
		 */
		public Builder(Set<ISubstitution> other) {
			substitutions = CollectionUtils.newLinkedHashSet();
			for (ISubstitution substitution : other) {
				try {
					substitutions.add(substitution.clone());
				} catch (CloneNotSupportedException e) {
					LOG.error("can't clone substitution, should never happen");
					throw new IllegalArgumentException(
							"substitution not clonable - " + other);
				}
			}
		}

		public Builder() {
			substitutions = CollectionUtils.newLinkedHashSet();
		}

		public Builder addSubstitution(ISubstitution substitution) {
			substitutions.add(substitution);
			return this;
		}

		public State build() {
			return new State(this); // NOPMD builder pattern
		}

	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((substitutions == null) ? 0 : substitutions.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final State other = (State) obj; // NOPMD generated code
		if (substitutions == null) {
			if (other.substitutions != null) {
				return false;
			}
		} else if (!substitutions.equals(other.substitutions)) {
			return false;
		}
		return true;
	}

	/**
	 * NOTE: this representation is subject to change and should never be used
	 * to perform serialization of this object (RP)
	 */
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("State:[");
		sb.append('\n');
		for (ISubstitution sub : substitutions) {
			if (sub.result()) {
				sb.append(sub);
				sb.append('\n');
			}
		}
		sb.append(']');
		return sb.toString();
	}

	@Override
	public boolean contains(IState other) {
		for (ISubstitution otherSubstitution : other.getSubstitutions()) {
			if (!this.substitutions.contains(otherSubstitution))
				return false;
		}
		return true;
	}

	// legacy pinko's code
	/*
	 * public boolean contains1(IState other) { boolean contains = true; for
	 * (ISubstitution otherSubstitution : other.getSubstitutions()) { boolean
	 * foundPredicate = false; for (ISubstitution substitution : substitutions)
	 * { final Predicate predicate = substitution.getPredicate(); final
	 * Predicate otherPredicate = otherSubstitution .getPredicate();
	 * foundPredicate = predicate.getName().equals( otherPredicate.getName());
	 * if (foundPredicate) { contains = substitution.equals(otherSubstitution);
	 * break; } }
	 * 
	 * if (!contains) { break; }
	 * 
	 * } return contains; }
	 */

	@Override
	public boolean isContradicting(IState other) {
		return other == null ? false
				: isContradicting(other.getSubstitutions());
	}

	@Override
	public IState merge(IState other) throws ContradictingStatesException {
		if (isContradicting(other)) {
			throw new ContradictingStatesException(
					"can't merge states, they contain contradicting substitutions");
		}
		final State.Builder builder = new State.Builder();
		for (ISubstitution substitution : substitutions) {
			builder.addSubstitution(substitution);
		}
		for (ISubstitution otherSubstitution : other.getSubstitutions()) {
			if (!substitutions.contains(otherSubstitution)) {
				builder.addSubstitution(otherSubstitution);
			}
		}
		return builder.build();
	}

	@Override
	public IState mergeAndOverride(IState other) {
		final State.Builder builder = new State.Builder();
		for (ISubstitution substitution : substitutions) {
			builder.addSubstitution(substitution);
		}
		for (ISubstitution otherSubstitution : other.getSubstitutions()) {
			if (!substitutions.contains(otherSubstitution)
					&& !isHaveNagateSubstitution(otherSubstitution)) {
				builder.addSubstitution(otherSubstitution);
			}
		}
		return builder.build();
	}

	@Override
	public boolean isSatisfying(Set<ISubstitution> constraintList) {
		for (ISubstitution substitution : substitutions) {
			final Predicate thisPred = substitution.getPredicate();
			for (ISubstitution constraint : constraintList) {
				final Predicate constraintPred = constraint.getPredicate();
				final boolean predNameMatch = thisPred.getName()
						.equalsIgnoreCase(constraintPred.getName());
				final boolean assignmentMatch = substitution
						.getSubstitutedConstants().equals(
								constraint.getSubstitutedConstants());
				final boolean operatorContradiction = thisPred.getOperator()
						.contradicts(constraintPred.getOperator());
				if (predNameMatch && assignmentMatch && operatorContradiction) {
					return false;
				}
			}
		}
		return true;
	}

	protected boolean isContradicting(Set<ISubstitution> constraintList) {
		boolean isContradicting = false;
		for (ISubstitution otherSubstitution : constraintList) {
			if (isContradicting(otherSubstitution)) {
				isContradicting = true;
				break;
			}
		}
		return isContradicting;
	}

	/**
	 * returns {@code true} if one of this state's substitutions contradicts
	 * this one
	 */
	protected boolean isContradicting(ISubstitution substitution) {
		for (ISubstitution s : substitutions) {
			if (s.isContradicting(substitution)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @param substitution
	 * @return True iff one of the state's substitutions negate this
	 *         substitution
	 */
	public boolean isHaveNagateSubstitution(ISubstitution substitution) {
		for (ISubstitution s : substitutions) {
			if (s.isNegateSubstitution(substitution)) {
				return true;
			}
		}
		return false;
	}

	public static IState createState(Set<ISubstitution> set) {

		final State.Builder builder = new Builder();
		for (ISubstitution sub : set) {
			builder.addSubstitution(sub);
		}
		return builder.build();
	}

	@Override
	public void swap(IState other) {
		State otherState = (State) other;
		this.swap(otherState);
	}

	private void swap(State other) {
		Set<ISubstitution> otherSet = other.substitutions;
		other.substitutions = this.substitutions;
		this.substitutions = otherSet;
	}

	@Override
	public IState clone() {
		State.Builder builder = new Builder(this.getSubstitutions());
		return builder.build();
	}

	@Override
	public boolean contains(ISubstitution sub) {
		return substitutions.contains(sub);
	}

	@Override
	public int getNumberOfContainedSubstitution(Set<ISubstitution> subs) {
		int subsCounter = 0;
		for (ISubstitution sub : subs) {
			if (this.contains(sub))
				subsCounter++;
		}
		return subsCounter;
	}

	public Set<ISubstitution> getNotContainedSubstitution(
			Set<ISubstitution> subs) {
		Set<ISubstitution> notContainedSubs = CollectionUtils
				.newLinkedHashSet();
		for (ISubstitution sub : subs) {
			if (!this.contains(sub))
				notContainedSubs.add(sub);
		}
		return notContainedSubs;
	}

}
